﻿using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Runtime.Versioning;
using System.Xml.Linq;
using System.Xml.XPath;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Communication.Local.LRPC;
using UtilZ.Dotnet.Ex.Communication.Net.Base;
using UtilZ.Dotnet.Ex.DataStruct;
using UtilZ.Dotnet.Ex.Json;
using UtilZ.Dotnet.Ex.Log;
using UtilZ.Tool.HostPluginBase;

namespace UtilZ.Tool.WatchdogPlugin
{
    /// <summary>
    /// 主机看门狗插件类
    /// </summary>
    public partial class Watchdog : HostPluginAbs
    {
        private readonly string _dataFilePath;
        private const string _VALUE = "Value";
        private readonly ConcurrentDictionary<string, AppItem> _appItemDic = new ConcurrentDictionary<string, AppItem>();
        private readonly ThreadEx _monotorThread;
        //private readonly ProcessMonitor _hostQueryProcessMonitor;

        private readonly AsynQueue<int> _heartFromPortAsynQueue;
        private readonly UDPUnpacker _unpacker;
        private readonly NetUdpClient _heartDataClient;

        /// <summary>
        /// 
        /// </summary>
        public Watchdog()
            : base("B0CC06B1-4A4C-4A20-B251-A0F08FE573D2", "看门狗")
        {
            string dir = PathEx.GetTypeAssemblyDirectory(this.GetType());
            this._dataFilePath = PathEx.Combine(PathType.Local, dir, "WatchdogData.xml");
            this._monotorThread = new ThreadEx(this.ProcessMonitorThreadMethod, "软件监视线程");

            //string hostQueryAppPath = WatchdogConfig.Instance.HostQueryAppName;
            //if (string.IsNullOrWhiteSpace(Path.GetPathRoot(hostQueryAppPath)))
            //{
            //    hostQueryAppPath = PathEx.Combine(PathType.Local, dir, WatchdogConfig.Instance.HostQueryAppName);
            //}
            //this._hostQueryProcessMonitor = new ProcessMonitor(hostQueryAppPath, -1, 5000);

            this._heartFromPortAsynQueue = new AsynQueue<int>(this.HeartFromPortAsynQueueCallback, new AsynQueueOptions());
            this._unpacker = new UDPUnpacker();
            this._unpacker.PackOutput += Unpacker_PackOutput;
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, WatchdogConfig.Instance.HeartPort);
            var initPara = new UdpClientInitPara(this._unpacker, endPoint);
            this._heartDataClient = new NetUdpClient(initPara);

            LRPCCore.CreateChannelA(WatchdogLRPCHelper.APP_CHANGED, this.PushAppStatusChangedNotify);

            LRPCCore.CreateChannelF(HostQueryHelper.QUERY_PRO_ARGS, GetProcessArgs);
            LRPCCore.CreateChannelF(HostQueryHelper.GET_CRUSH_WINDOW_LIST, GetCrushWindowInfoList);
            LRPCCore.CreateChannelF(HostQueryHelper.IS_NO_RESONSE, IsNoResponse);
        }

        private void HeartFromPortAsynQueueCallback(int fromPort)
        {
            try
            {
                List<int> pidList = ProcessEx.GetProcessIdByPort(fromPort, true);
                if (pidList == null || pidList.Count == 0)
                {
                    return;
                }

                foreach (var app in this._appItemDic.Values)
                {
                    if (app.ProcessId == pidList[0])
                    {
                        app.UpdateHeartTimestamp();
                    }
                }
            }
            catch (Exception ex)
            {
                Loger.Error(ex);
            }
        }

        private void Unpacker_PackOutput(object sender, Dotnet.Ex.Communication.Base.DataOutputArgs e)
        {
            try
            {
                int fromPort = ((IPEndPoint)((NetLinkInfo)e.LinkInfo).RemoteEndPoint).Port;
                this._heartFromPortAsynQueue.Enqueue(fromPort);
            }
            catch (Exception ex)
            {
                Loger.Error(ex);
            }
        }

        private object IsNoResponse(object obj)
        {
            return base._OSAdapter.IsNoResponse((IntPtr)obj);
        }

        private object GetCrushWindowInfoList(object obj)
        {
            return base._OSAdapter.GetCrushProcessIdList();
        }

        private object GetProcessArgs(object obj)
        {
            int pid = (int)obj;
            return base._OSAdapter.QueryProcessStartArgs(pid);
        }

        private void PushAppStatusChangedNotify(object message)
        {
            try
            {
                string json = JsonSerializer.SerializeObject(message);
                base.OnRaiseSendWebsocketMessage(json);
            }
            catch (Exception ex)
            {
                Loger.Error(ex);
            }
        }


        /// <summary>
        /// 初始化插件
        /// </summary>
        protected override void PrimitiveInit()
        {
            this.LoadData();
        }

        private void LoadData()
        {
            if (!File.Exists(this._dataFilePath))
            {
                return;
            }

            try
            {
                var xdoc = XDocument.Load(this._dataFilePath);
                var appItemEles = xdoc.XPathSelectElements(@"//AppItem");
                foreach (var appItemEle in appItemEles)
                {
                    var appItem = new AppItem();

                    appItem.IsMonitor = XmlEx.GetChildXElementValue<bool>(appItemEle, "IsMonitor", _VALUE);
                    appItem.AppName = XmlEx.GetChildXElementValue(appItemEle, "AppName", _VALUE);
                    appItem.Arguments = XmlEx.GetChildXElementValue(appItemEle, "Arguments", _VALUE);
                    appItem.AppExeFilePath = XmlEx.GetChildXElementValue(appItemEle, "AppExeFilePath", _VALUE);
                    appItem.Des = XmlEx.GetChildXElementValue(appItemEle, "Des", _VALUE);
                    appItem.DelayStartTime = XmlEx.GetChildXElementValue<int>(appItemEle, "DelayStartTime", _VALUE);
                    appItem.EnableHeartCheck = XmlEx.GetChildXElementValue<bool>(appItemEle, "EnableHeartCheck", _VALUE);
                    appItem.HeartTimeoutSeconds = XmlEx.GetChildXElementValue<int>(appItemEle, "HeartTimeoutSeconds", _VALUE);
                    appItem.Init();

                    this._appItemDic.Add(appItem.AppName, appItem);
                }
            }
            catch (Exception ex)
            {
                Loger.Error(ex, $"看门狗数据文件\"{this._dataFilePath}\"加载异常");
            }
        }

        private void SaveData()
        {
            try
            {
                var xdoc = new XDocument();
                var rootEle = new XElement("root");

                foreach (var appItem in this._appItemDic.Values.ToArray())
                {
                    var appItemEle = new XElement("AppItem");

                    appItemEle.Add(XmlEx.CreateXElement("IsMonitor", _VALUE, appItem.IsMonitor.ToString()));
                    appItemEle.Add(XmlEx.CreateXElement("AppName", _VALUE, appItem.AppName));
                    appItemEle.Add(XmlEx.CreateXElement("Arguments", _VALUE, appItem.Arguments));
                    appItemEle.Add(XmlEx.CreateXElement("AppExeFilePath", _VALUE, appItem.AppExeFilePath));
                    appItemEle.Add(XmlEx.CreateXElement("Des", _VALUE, appItem.Des));
                    appItemEle.Add(XmlEx.CreateXElement("DelayStartTime", _VALUE, appItem.DelayStartTime.ToString()));
                    appItemEle.Add(XmlEx.CreateXElement("EnableHeartCheck", _VALUE, appItem.EnableHeartCheck.ToString()));
                    appItemEle.Add(XmlEx.CreateXElement("HeartTimeoutSeconds", _VALUE, appItem.HeartTimeoutSeconds.ToString()));

                    rootEle.Add(appItemEle);
                }

                xdoc.Add(rootEle);

                DirectoryInfoEx.CheckFilePathDirectory(this._dataFilePath);
                xdoc.Save(this._dataFilePath);
            }
            catch (Exception ex)
            {
                Loger.Error(ex, $"保存看门狗数据到文件文件\"{this._dataFilePath}\"异常");
            }
        }





        private void ProcessMonitorThreadMethod(ThreadExPara threadPara)
        {
            try
            {
                const int INTERVAL = 100;
                long millisecondsTimeout = WatchdogConfig.Instance.MonitiorCheckMillisecondsInterval;
                long lastChecktimestamp = -1L, timestamp;

                while (!threadPara.Token.IsCancellationRequested)
                {
                    try
                    {
                        threadPara.WaitOne(INTERVAL);

                        timestamp = TimeEx.GetTimestamp();
                        if (timestamp - lastChecktimestamp > millisecondsTimeout)
                        {
                            lastChecktimestamp = timestamp;

                            //this._hostQueryProcessMonitor.Check();//检查主机查询进程
                            this.CheckApp();//检查应用进程运行状态
                        }
                        else
                        {
                            foreach (var appItem in this._appItemDic.Values.ToArray())
                            {
                                appItem.UpdateProcessResourseUsageRate();
                            }
                        }
                    }
                    catch (Exception exi)
                    {
                        Loger.Error(exi);
                    }
                }
            }
            catch (Exception ex)
            {
                Loger.Error(ex, "看门狗进程监视线程异常");
            }
        }

        private void CheckApp()
        {
            //应用进程检查
            List<int> crushProcessIdList = HostQueryHelper.GetCrushWindowInfoList();
            bool existDelayStart = false;
            foreach (var appItem in this._appItemDic.Values)
            {
                if (appItem.IsMonitor)
                {
                    try
                    {
                        if (appItem.Check(crushProcessIdList))
                        {
                            existDelayStart = true;
                        }
                        else
                        {
                            appItem.UpdateProcessResourseUsageRate();
                        }
                    }
                    catch (Exception ex)
                    {
                        Loger.Warn(ex, $"检测应用\"{appItem.AppName}\"异常");
                    }
                }
            }

            if (existDelayStart)
            {
                //延迟启动应用全部重新启动
                AppItem[] restartAppItems = this._appItemDic.Values.Where(t => { return t.IsDelayStart(); }).ToArray();
                this._firstAppStartTimestamp = TimeEx.GetTimestamp();
                foreach (var restartAppItem in restartAppItems)
                {
                    try
                    {
                        restartAppItem.Reset();
                        restartAppItem.StartCheck(this._firstAppStartTimestamp);
                    }
                    catch (Exception ex)
                    {
                        Loger.Warn(ex, $"重启应用\"{restartAppItem.AppName}\"异常");
                    }
                }
            }
        }





        /// <summary>
        /// 第一个应用启动时间戳
        /// </summary>
        private long _firstAppStartTimestamp = 0;

        /// <summary>
        /// 启动
        /// </summary>
        protected override void PrimitiveStart()
        {
            //this._hostQueryProcessMonitor.Start();
            this.StartCheck();
            this.CheckMonitorThread();
            this._heartFromPortAsynQueue.Start();
            this._heartDataClient.Start();
            Loger.Info("监视线程已启动...");
        }



        private void CheckMonitorThread()
        {
            if (this._appItemDic.Count > 0)
            {
                if (!this._monotorThread.IsRuning)
                {
                    this._monotorThread.Start();
                }
            }
            else
            {
                if (this._monotorThread.IsRuning)
                {
                    this._monotorThread.Stop();
                }
            }
        }


        private void StartCheck()
        {
            if (this._appItemDic.Count > 0)
            {
                int maxDelayStartTime = this._appItemDic.Values.Max(t => { return t.DelayStartTime; });
                if (maxDelayStartTime > 0)
                {
                    //检查是否所有应用都已启动,如果都已启动.则第一次应用启动时间就设置为延迟时间之外
                    bool allAppRuning = true;
                    foreach (var appItem in this._appItemDic.Values)
                    {
                        if (!appItem.AppIsRuning())
                        {
                            allAppRuning = false;
                            break;
                        }
                    }

                    if (allAppRuning)
                    {
                        this._firstAppStartTimestamp = TimeEx.GetTimestamp() - maxDelayStartTime * 2000;
                    }
                    else
                    {
                        this._firstAppStartTimestamp = TimeEx.GetTimestamp();
                    }
                }
            }
            else
            {
                this._firstAppStartTimestamp = TimeEx.GetTimestamp();
            }

            foreach (var appItem in this._appItemDic.Values)
            {
                try
                {
                    if (appItem.IsMonitor)
                    {
                        //监视,启动检查
                        appItem.Reset();
                        appItem.Init();
                        appItem.StartCheck(this._firstAppStartTimestamp);
                    }
                    else
                    {
                        //未监视，忽略
                        appItem.NoMonitorUpdateProcess();
                    }
                }
                catch (Exception ex)
                {
                    //某个特殊情况下,会出个不确定异常,至少曾经出现过
                    Loger.Warn(ex);
                }
            }
        }

        /// <summary>
        /// 获取插件web资源信息,为null则表示无相关信息
        /// </summary>
        /// <returns>插件web资源信息,为null则表示无相关信息</returns>
        protected override WebResourceInfo PrimitiveGetWebResourceInfo()
        {
            var appDir = PathEx.GetTypeAssemblyDirectory(this.GetType());
#if DEBUG
            var dirInfo = new DirectoryInfo(PathEx.GetEntryAssemblyDirectory());
            while (!string.Equals(dirInfo.Name, "App", StringComparison.OrdinalIgnoreCase))
            {
                dirInfo = dirInfo.Parent;
            }
            string webRootDir = PathEx.Combine(PathType.Local, dirInfo.Parent.FullName, @"Plugins\UtilZ.Tool.WatchdogPlugin\htmls");
#else
            string webRootDir = PathEx.Combine(PathType.Local, appDir, "htmls");
#endif

            return new WebResourceInfo("看门狗", "软件看门狗", "watchdog.html")
            {
                EnableWebsocket = true,
                ApiObjs = new object[] { this },
                HttpSourcePaths = new string[] { webRootDir }
            };
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            //this._hostQueryProcessMonitor.Dispose();
            this._heartDataClient.Dispose();
            this._heartFromPortAsynQueue.Dispose();
            this.SaveData();
        }

        ///// <summary>
        ///// WebSocket接收消息处理
        ///// </summary>
        ///// <param name="webSocket">WebSocketClient</param>
        ///// <param name="message">接收到的消息</param>
        //protected override void PrimitiveWebSocketReceiveMessage(WebSocketClient webSocket, ReceiveWebsocketMessage message)
        //{
        //    try
        //    {
        //        //注:看门狗这里只发送数据,不接收数据,所以不用处理收到的数据

        //        //message.Data
        //        //var msg = e.Message;
        //        //switch (msg.MessageType)
        //        //{
        //        //    case WebSocketMessageTypes.Text:
        //        //        Loger.Info(((WebSocketTextMessage)msg).Text);

        //        //        //string filePath = @"D:\Projects\Posture\功能指标.txt";
        //        //        //filePath = @"D:\1867.txt";
        //        //        //filePath = @"D:\tmp\google-earth-dbroot-parser.js";
        //        //        //string text = File.ReadAllText(filePath);

        //        //        //string text = $"WebSocket建立{TimeEx.GetTimestamp()}";
        //        //        //webSocket.Send(text);

        //        //        ////webSocket.Send($"WebSocket建立{TimeEx.GetTimestamp()}");

        //        //        ////webSocket.Ping();

        //        //        //byte[] data = new byte[] { 1, 2, 3, 4, 5, 6 };
        //        //        //webSocket.Send(data);
        //        //        break;
        //        //    case WebSocketMessageTypes.Binary:
        //        //        Loger.Info("收到二进制消息");
        //        //        break;
        //        //    default:
        //        //        Loger.Info($"收到{msg.MessageType.ToString()}消息");
        //        //        break;
        //        //}
        //    }
        //    catch (Exception ex)
        //    {
        //        Loger.Error(ex);
        //    }
        //}



    }
}